﻿using System;
using System.Windows;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using Bling.DSL;
using Bling.WPF;
using Bling.Util;
using Bling.Physics;
using Bling.Animation;

namespace Bling.PhysicsTutorial {
  /// <summary>
  /// Demonstrate higher level physics through ParticleBody and ShapeBody. 
  /// </summary>
  public class ShapeIntro : PhysicsCanvas {
    // Extend ParticlBody.Is to get particles for each body, pre-req for ShapeBody.Is
    // Extend ShapeBody.Is to get shape matching functionality
    public class CustomBlock : PhysicsBlock<Image, ImageBl, CustomBlock>,
      ParticleBody.Is<CustomBlock>, ShapeBody.Is<CustomBlock>, RigidBody.Is<CustomBlock> {
      public CustomBlock(World World, int Count, Func<int, Image> MakeT) : base(World, Count, MakeT) {
        // Must manually install particle body and shape body extensions. 
        new ParticleBody.Impl<CustomBlock>(this);
        new RigidBody.Impl<CustomBlock>(this);
        new ShapeBody.Impl<CustomBlock>(this);
      }
    }
    // image sources to use in example. 
    public static readonly ImageSourceBl[] Covers = new ImageSourceBl[] {
      Properties.Resources.songs_1_2,
      Properties.Resources.songs_2_2,
      Properties.Resources.songs_3_2,
      Properties.Resources.songs_4_2,
      Properties.Resources.songs_5_2,
      Properties.Resources.songs_6_2,
      Properties.Resources.songs_7_2,
      Properties.Resources.songs_8_2,
      Properties.Resources.songs_1_2,
    };
    static ShapeIntro() {
      // 4 particles per body is the default, this statement isn't necessary.
      // Change 4 to another number if you want different number of particles for the block.
      // ParticlesCount must be configured statically because used to create dependency properties. 
      ParticleBody.Impl<CustomBlock>.ParticlesCount = 4;
    }

    public ShapeIntro() : base("ShapeIntro", 10) {

    }
    protected override Action Init(CanvasBl MainCanvas, World World) {
      CustomBlock block = new CustomBlock(World, Covers.Length, (idx) => {
        return new ImageBl(MainCanvas) {
          Source = Covers[idx],
          // Allows us to set the center rotation of an image directly, as opposed through a render transform
          // which won't work in a physics block. 
        };
      }) { Name = "ShapeIntro", };

      // A block of thumbs will control the positions of each particle position.
      PlainBlock<Thumb, ThumbBl> Thumbs = new PlainBlock<Thumb, ThumbBl>(World, block.Count * 4, (idx) => {
        var j = idx % 4;
        return new ThumbBl(MainCanvas) {
          ZIndex = 2, Background = j.SelectColor(), CanDrag = true,
        };
      }) {
        Name = "Thumbs",
      };
      PlainBlock<Thumb, ThumbBl> ThumbsX = new PlainBlock<Thumb, ThumbBl>(World, block.Count, (idx) => {
        var j = idx;
        return new ThumbBl(MainCanvas) {
          ZIndex = 2, Background = j.SelectColor(), CanDrag = false,
        };
      }) {
        Name = "ThumbsX",
      };
      // Bind the shape of an image, in this case a rectangle.
      block.ShapePoints().BindRect(Covers[0].Size);
      DoubleBl[] CenterWeights = new DoubleBl[4];
      {
        PointBl p = new PointBl(0, 0);
        for (int i = 0; i < 4; i++) {
          var slider = new LabelSliderBl(MainCanvas) {
            Minimum = 1, Maximum = 100, Value = 1, LabelName = "P" + i,
            Label = { Foreground = i.SelectColor(), Background = Brushes.Transparent }, LeftTop = p,
          };
          CenterWeights[i] = slider;
          p = slider.LeftBottom;
        }
      }
      block.ShapeBodyE().ParticleCenterWeight = i => CenterWeights[i];
      block.ShapeBodyE().ShapeStrength = (body) => 1d;

      // Alternatively, each point can be specified explicitly:
      if (false) {
        block.ShapePoints().Bind(new PointBl(0, 0), new PointBl(Covers[0].Size.X, 0),
          Covers[0].Size, new PointBl(0, Covers[0].Size.Y));
      }
      // TIP: access members of the shape body extension through the ShapeBodyE method.
      // RelaxNext is called after center/rotation has been computed but before particle
      // positions are set. Assign if you want to manipulate center/rotation before
      // particle positions are set.
      block.ShapeBodyE().RelaxNext = (block0) => {
        block0.ForAll(body => {
          // re-orient.
          body.Rotation().Relax[.01] = 0.ToPI();

          // BAD: center doesn't have its own momentum, so we can do anything with restitution.
          if (false)
            body.Center().Bind = body.Center().Clamp(new Point(0, 0), MainCanvas.Size);
        });
      };

      // CustomInit happens after all extensions are initialized, constraints
      // expressed here are added after particle positions are set.
      block.CustomInit = (block0) => {
        block0.ConnectBlock();
        block0.ForAll(body => {
          body.Center().Link.To = ThumbsX[body.Index].At.CenterPosition;

          // A short cut to connect rotation, center of each body's particles to UI widget. 
          body.Particles().ForAll(particle => {
            var actual = body.Index * 4 + particle.Index;
            PointBl r = new Random();
            // initialize particle positions to random point.
            // TIP: Cannot use MainCanvas.Size since size is not computed until after initialization
            particle.Position.Init = (new PointBl(1000, 1000) * r);
            // Allow thumb to manipulate position when it is being dragged.
            particle.Position.Link[!Thumbs[actual].At.IsDragging] =
              Thumbs[actual].At.CenterPosition;
            // NOTE: we don't have to add the verlet step, this is already added by the ParticleBody extension.
            // As always, restitution and clamp.
            particle.Position.Old = particle.Position.Old.Restitution(particle.Position, new Point(0, 0), MainCanvas.Size);
            particle.Position = particle.Position.Clamp(new PointBl(0, 0), MainCanvas.Size);
          });
        });
      };
      // no post code generation action.
      return () => { };
    }
  }

}
